﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 * 
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      http://www.iti.illinois.edu / http://csl.illinois.edu
 * 
 *      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 *      associated documentation files (the “Software”), to deal with the Software without restriction, 
 *      including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *      and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 *      subject to the following conditions:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *      following disclaimers.
 *      
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *      the following disclaimers in the documentation and/or other materials provided with the distribution.
 *
 *      Neither the names of Information Trust institute, Coordinated Science Laboratory, University of Illinois,
 *      The Board of Trustees of the University of Illinois nor the names of its contributors may be used to 
 *      endorse or promote products derived from this Software without specific prior written permission.
 *      
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *      
 \*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using Disruptor;

namespace SIEGate.Core.MessageBusses
{
    class ChannelMessageBusConsumer
    {
        private ChannelMessageBusProducer m_producer;
        private ISequenceBarrier m_frontBarrier;
        private bool m_enabled = false;
        private Sequence m_whereAmI = new Sequence(Sequencer.InitialCursorValue);
        internal TimeSpan Waittimeout = new TimeSpan(1);
        public ISet<Guid> AvailableStreamIDs
        {
            get
            {
                return m_producer.StreamIDs;
            }
        }
        public ISet<Guid> SubscribedStreamIDs { get; private set; }

        public Sequence Sequence
        {
            get { return m_whereAmI; }
        }

        public IMeasurement Recv()
        {
            if (!SubscribedStreamIDs.Any()) { return null; }
            while (true)
            {
                long recSeq = m_whereAmI.Value + 1L;
                m_frontBarrier.WaitFor(recSeq);
                IMeasurement res = null;
                while (res == null) res = m_producer.RingBuff[recSeq].data;
                m_whereAmI.LazySet(recSeq);
                if (SubscribedStreamIDs.Contains(res.StreamID))
                {
                    return res;
                }
            }
        }

        public IMeasurement[] RecvMany(int max, bool block = false)
        {
            // NOTE we need sanity code for long wraps in sequences,  however at 2million
            // measurements per second through the buffer it would take almost 30K years to wrap the long...
            long nextSeq = m_whereAmI.Value + 1L;

            // WaitFor with a timeout will return a value < nextSeq if it times out
            // in that case we got nothing, so just be done.
            long actualSeq;
            if (block)
            {
                actualSeq = m_frontBarrier.WaitFor(nextSeq);
            }
            else
            {
                actualSeq = m_frontBarrier.WaitFor(nextSeq, Waittimeout);

                if (actualSeq < nextSeq)
                {
                    return new IMeasurement[0];
                }
            }
            int asize;

            if (max != 0)
            {
                asize = max;
            }
            else
            {
                asize = (int)(actualSeq - m_whereAmI.Value);
            }

            int i = 0;
            IMeasurement m;
            // prolly won't fill, could, so just avoid resize
            List<IMeasurement> res = new List<IMeasurement>(asize);
            // this will stop filling values at max size or the front of the queue, whatever comes first.
            while (nextSeq <= actualSeq && i < asize)
            {
                while (true)
                {
                    m = m_producer.RingBuff[nextSeq].data;
                    if (m != null) break;
                }
                if (SubscribedStreamIDs.Contains(m.StreamID))
                {
                    res.Add(m);
                    i++;
                }
                nextSeq++;
            }
            m_whereAmI.LazySet(nextSeq - 1L);
            //are arrays stack or reference type for return. do we want to do list or IEnumberable instead?
            return res.ToArray<IMeasurement>();
        }

        public void Enable()
        {
            if (!m_enabled)
            {
                m_whereAmI.LazySet(m_producer.RingBuff.Cursor);
                m_producer.AddConsumer(this);
                m_enabled = true;
            }
        }
        public void Disable()
        {
            m_producer.RemoveConsumer(this);
            m_enabled = false;
        }

        public ISet<Guid> Subscribe(ISet<Guid> streamIDs)
        {
            HashSet<Guid> subsForMe = new HashSet<Guid>(AvailableStreamIDs.Intersect(streamIDs));

            foreach (Guid signal in subsForMe)
            {
                SubscribedStreamIDs.Add(signal);
                if (m_enabled) m_producer.AddReference(signal);
            }
            Enable();
            return subsForMe;
        }

        public ISet<Guid> Unsubscribe(ISet<Guid> streamIDs)
        {
            HashSet<Guid> unsubsForMe = new HashSet<Guid>(SubscribedStreamIDs.Intersect(streamIDs));
            foreach (Guid signal in unsubsForMe)
            {
                SubscribedStreamIDs.Remove(signal);
                m_producer.RemoveReference(signal);
            }
            if (!SubscribedStreamIDs.Any())
            {
                Disable();
            }
            return unsubsForMe;
        }

        public ChannelMessageBusConsumer(ChannelMessageBusProducer myProducer, ISet<Guid> mySubs)
        // ISet<Guid> streamIDs, ChannelMessageBus mb, ISequenceBarrier front, long startseq)
        {
            m_producer = myProducer;
            m_frontBarrier = m_producer.RingBuff.NewBarrier();
            SubscribedStreamIDs = new HashSet<Guid>();

            //Subscribe(mySubs);
            Enable();
        }
    }
}
